package com.listeningframework.boot.autoconfigure.security.overrides;

/*
 * Copyright 2002-2013 the original author or authors.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

/**
 * Configures an
 * {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder}
 * to have JDBC authentication. It also allows easily adding users to the
 * database used for authentication and setting up the schema.
 *
 * <p>
 * The only required method is the {@link #dataSource(DataSource)} all other
 * methods have reasonable defaults.
 *
 * @param <B>
 *            the type of the {@link ProviderManagerBuilder} that is being
 *            configured
 *
 * @author Rob Winch
 * @since 3.2
 */
public class DefaultsJdbcUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
		extends UserDetailsManagerConfigurer<B, JdbcUserDetailsManagerConfigurer<B>> {
	/**
	 * Default Location
	 */
	private static final String DEFAULT_LOCATION = "META-INF/security/";
	/**
	 * Default Schema Location
	 */
	private String defualtSchemaLocation = DEFAULT_LOCATION;
	/**
	 * Default Schema Name
	 */
	private String defaultSchemaName = "security_official_schema.sql";
	/**
	 * Default Data Name
	 */
	private String defaultDataName = "security_official_data.sql";
	/**
	 * DataSource
	 */
	private DataSource dataSource;
	/**
	 * Data Source Platform
	 */
	private String dataSourcePlatform;
	/**
	 * Init Default User
	 */
	private boolean withDefaultUser = true;
	/**
	 * Scripts List
	 */
	private List<Resource> initScripts = new ArrayList<Resource>();

	public DefaultsJdbcUserDetailsManagerConfigurer(JdbcUserDetailsManager manager) {
		super(manager);
	}

	public DefaultsJdbcUserDetailsManagerConfigurer() {
		this(new JdbcUserDetailsManager());
	}

	/**
	 * Populates the {@link DataSource} to be used. This is the only required
	 * attribute.
	 *
	 * @param dataSource the {@link DataSource} to be used. Cannot be null.
	 * @return DefaultsJdbcUserDetailsManagerConfigurer
	 * @throws Exception 异常
	 */
	public DefaultsJdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource) throws Exception {
		this.dataSource = dataSource;
		getUserDetailsService().setDataSource(dataSource);
		return this;
	}

	/**
	 * Sets the query to be used for finding a user by their username. For
	 * example:
	 *
	 * <code>
	 *     select username,password,enabled from users where username = ?
	 * </code>
	 *
	 * @param query 用户信息查询SQL
	 * @return DefaultsJdbcUserDetailsManagerConfigurer
	 * @throws Exception 异常
	 */
	public DefaultsJdbcUserDetailsManagerConfigurer<B> usersByUsernameQuery(String query) throws Exception {
		getUserDetailsService().setUsersByUsernameQuery(query);
		return this;
	}

	/**
	 * Sets the query to be used for finding a user's authorities by their
	 * username. For example:
	 *
	 * <code>
	 *     select username,authority from authorities where username = ?
	 * </code>
	 *
	 * @param query 查询语句
	 * @return DefaultsJdbcUserDetailsManagerConfigurer
	 * @throws Exception 异常
	 */
	public DefaultsJdbcUserDetailsManagerConfigurer<B> authoritiesByUsernameQuery(String query) throws Exception {
		getUserDetailsService().setAuthoritiesByUsernameQuery(query);
		return this;
	}

	/**
	 * DataSource Platform
	 *
	 * @param platform 平台
	 * @return OfficialJdbcUserDetailsManagerConfigurer
	 * @throws Exception 异常
	 */
	public DefaultsJdbcUserDetailsManagerConfigurer<B> withDataSourcePlatform(String platform) throws Exception {
		this.dataSourcePlatform = platform;
		return this;
	}

	/**
	 * An SQL statement to query user's group authorities given a username. For
	 * example:
	 *
	 * <code>
	 *     select
	 *         g.id, g.group_name, ga.authority
	 *     from
	 *         groups g, group_members gm, group_authorities ga
	 *     where
	 *         gm.username = ? and g.id = ga.group_id and g.id = gm.group_id
	 * </code>
	 *
	 * @param query 查询语句
	 * @return DefaultsJdbcUserDetailsManagerConfigurer
	 * @throws Exception 设置失败
	 */
	public DefaultsJdbcUserDetailsManagerConfigurer<B> groupAuthoritiesByUsername(String query) throws Exception {
		JdbcUserDetailsManager userDetailsService = getUserDetailsService();
		userDetailsService.setEnableGroups(true);
		userDetailsService.setGroupAuthoritiesByUsernameQuery(query);
		return this;
	}

	/**
	 * A non-empty string prefix that will be added to role strings loaded from
	 * persistent storage (default is "").
	 *
	 * @param rolePrefix 角色前缀
	 * @return DefaultsJdbcUserDetailsManagerConfigurer
	 * @throws Exception 设置异常
	 */
	public DefaultsJdbcUserDetailsManagerConfigurer<B> rolePrefix(String rolePrefix) throws Exception {
		getUserDetailsService().setRolePrefix(rolePrefix);
		return this;
	}

	/**
	 * Defines the {@link UserCache} to use
	 *
	 * @param userCache 用户缓存
	 * @return DefaultsJdbcUserDetailsManagerConfigurer
	 * @throws Exception 异常
	 */
	public DefaultsJdbcUserDetailsManagerConfigurer<B> userCache(UserCache userCache) throws Exception {
		getUserDetailsService().setUserCache(userCache);
		return this;
	}

	@Override
	protected void initUserDetailsService() throws Exception {
		if (!initScripts.isEmpty()) {
			getDataSourceInit().afterPropertiesSet();
		}
		super.initUserDetailsService();
	}

	@Override
	public JdbcUserDetailsManager getUserDetailsService() {
		return (JdbcUserDetailsManager) super.getUserDetailsService();
	}

	public DefaultsJdbcUserDetailsManagerConfigurer<B> setDefualtSchemaLocation(String defualtSchemaLocation) {
		this.defualtSchemaLocation = defualtSchemaLocation;
		return this;
	}

	public DefaultsJdbcUserDetailsManagerConfigurer<B> setDefaultSchemaName(String defaultSchemaName) {
		this.defaultSchemaName = defaultSchemaName;
		return this;
	}

	/**
	 * Populates the default schema that allows users and authorities to be
	 * stored.
	 *
	 * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
	 *         customizations
	 */
	public DefaultsJdbcUserDetailsManagerConfigurer<B> withDefaultSchema() {
		Assert.notNull(dataSourcePlatform, "DataSource Platform is Needed");
		this.initScripts.add(getSchemaScripts(defaultSchemaName));
		if (withDefaultUser) {
			this.initScripts.add(getSchemaScripts(defaultDataName));
		}
		return this;
	}

	private Resource getSchemaScripts(String scripts) {
		return new ClassPathResource(defualtSchemaLocation + scripts);
	}

	protected DatabasePopulator getDatabasePopulator() {
		ResourceDatabasePopulator dbp = new ResourceDatabasePopulator();
		dbp.setScripts(initScripts.toArray(new Resource[initScripts.size()]));
		dbp.setContinueOnError(true);
		return dbp;
	}

	private DataSourceInitializer getDataSourceInit() {
		DataSourceInitializer dsi = new DataSourceInitializer();
		dsi.setDatabasePopulator(getDatabasePopulator());
		dsi.setDataSource(dataSource);
		return dsi;
	}
}
